package org.jretty.server.core.webapp;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.jetty.webapp.WebAppClassLoader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;

import dalvik.system.DexClassLoader;


/**
 * AndroidClassLoader
 * <p>
 * Loads classes dynamically from dex files wrapped inside a zip.
 */
@SuppressWarnings("unchecked")
public class AndroidClassLoader extends ClassLoader //extends WebAppClassLoader
{
    private WebAppContext _context;
    private ClassLoader _parent;
    private ClassLoader _delegate;
    private String _path = "";

    public AndroidClassLoader(String path, ClassLoader parent, WebAppContext context) throws IOException {
        this(parent, context);
        _path = path;
    }

    public AndroidClassLoader(WebAppContext context)
            throws IOException {
        this(ClassLoader.getSystemClassLoader(), context);
    }

    public AndroidClassLoader(ClassLoader parent, WebAppContext context)
            throws IOException {
        //super(parent, context);
        _parent = parent;
        _context = context;
    }

    public WebAppContext getContext() {
        return _context;
    }

    public void init()
            throws IOException {
        if (_path == null || "".equals(_path.trim()))
            _delegate = new DexClassLoader("", ((WebAppContext) getContext()).getTempDirectory().getCanonicalPath(), null, _parent);
        else
            _delegate = new DexClassLoader(_path, ((WebAppContext) getContext()).getTempDirectory().getCanonicalPath(), null, _parent);

        if (Log.isDebugEnabled())
            Log.debug("Android webapp classloader path= " + _path + " tmpdir=" + ((WebAppContext) getContext()).getTempDirectory() + " dexloader = " + _delegate + " parentloader=" + _parent);
    }

    public void addClassPath(Resource resource)
            throws IOException {
        if (resource instanceof ResourceCollection) {
            for (Resource r : ((ResourceCollection) resource).getResources())
                addClassPath(r);
        } else {
            addClassPath(resource.getFile().getAbsolutePath());
        }
    }

    /**
     * Accept a pre-made classpath.
     * NOTE: the path elements must be separated by ":" chars, not ";"
     *
     * @see WebAppClassLoader#addClassPath(String)
     */
    public void addClassPath(String classPath) throws IOException {
        if (classPath == null)
            return;

        if (!"".equals(_path) && !_path.endsWith(":"))
            _path += ":";

        _path += classPath;
        Log.debug("Path = " + _path);
    }

    public void addJars(Resource lib) {
        if (lib.exists() && lib.isDirectory()) {
            String[] files = lib.list();
            for (int f = 0; files != null && f < files.length; f++) {
                try {
                    Resource fn = lib.addPath(files[f]);
                    String fnlc = fn.getName().toLowerCase();

                    if (!fn.isDirectory() && isAndroidArchive(fnlc)) {
                        String jar = fn.getFile().getAbsolutePath();
                        addClassPath(jar);
                    }
                } catch (Exception ex) {
                    Log.warn(Log.EXCEPTION, ex);
                }
            }
        }
    }

    protected boolean isAndroidArchive(String filename) {
        int dot = filename.lastIndexOf('.');
        if (dot == -1)
            return false;

        String extension = filename.substring(dot);
        return ".zip".equals(extension) || ".apk".equals(extension);
    }

    // add by zollty
    public Enumeration<URL> findResources(String name) throws IOException {
        return _delegate.getResources(name);
    }

    // add by zollty
    protected URL findResource(String name) {
        return _delegate.getResource(name);
    }

    public Enumeration<URL> getResources(String name) throws IOException {
        boolean system_class = _context.isSystemClass(name);
        boolean server_class = _context.isServerClass(name);

        List<URL> from_parent = toList(server_class ? null : _parent.getResources(name));
        List<URL> from_webapp = toList((system_class && !from_parent.isEmpty()) ? null : this.findResources(name));

        if (_context.isParentLoaderPriority()) {
            from_parent.addAll(from_webapp);
            return Collections.enumeration(from_parent);
        }
        from_webapp.addAll(from_parent);
        return Collections.enumeration(from_webapp);
    }

    private List<URL> toList(Enumeration<URL> e) {
        List<URL> list = new ArrayList<URL>();
        while (e != null && e.hasMoreElements())
            list.add(e.nextElement());
        return list;
    }

    public URL getResource(String name) {
        URL url = null;
        boolean tried_parent = false;
        boolean system_class = _context.isSystemClass(name);
        boolean server_class = _context.isServerClass(name);

        if (system_class && server_class)
            return null;

        if (_parent != null && (_context.isParentLoaderPriority() || system_class) && !server_class) {
            tried_parent = true;

            if (_parent != null)
                url = _parent.getResource(name);
        }

        if (url == null) {
            url = this.findResource(name);

            if (url == null && name.startsWith("/")) {
                if (Log.isDebugEnabled())
                    Log.debug("HACK leading / off " + name);
                url = this.findResource(name.substring(1));
            }
        }

        if (url == null && !tried_parent && !server_class) {
            if (_parent != null)
                url = _parent.getResource(name);
        }

        if (url != null)
            if (Log.isDebugEnabled())
                Log.debug("getResource(" + name + ")=" + url);

        return url;
    }

    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        ClassNotFoundException ex = null;
        boolean tried_parent = false;

        boolean system_class = ((WebAppContext) getContext()).isSystemClass(name);
        boolean server_class = ((WebAppContext) getContext()).isServerClass(name);

        if (system_class && server_class) {
            return null;
        }

        if (c == null && _parent != null && (((WebAppContext) getContext()).isParentLoaderPriority() || system_class) && !server_class) {
            tried_parent = true;
            try {
                c = _parent.loadClass(name);
                if (Log.isDebugEnabled())
                    Log.debug("loaded " + c);
            } catch (ClassNotFoundException e) {
                ex = e;
            }
        }

        if (c == null) {
            try {
                if (_delegate != null) {
                    if (Log.isDebugEnabled())
                        Log.debug("loading class " + name + " trying delegate loader" + _delegate);
                    c = _delegate.loadClass(name);
                    if (Log.isDebugEnabled()) Log.debug("delegate loaded " + c);
                }
            } catch (ClassNotFoundException e) {
                ex = e;
            }
        }

        if (c == null && _parent != null && !tried_parent && !server_class)
            c = _parent.loadClass(name);

        if (c == null)
            throw ex;

        if (resolve)
            resolveClass(c);

        if (Log.isDebugEnabled())
            Log.debug("loaded " + c + " from " + c.getClassLoader());

        return c;
    }

    @Override
    public String toString() {
        return "(AndroidClassLoader, delegate=" + _delegate + ")";
    }
}
